package com.fujin.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.fujin.gulimall.product.Vo.Catelog2Vo;
import com.fujin.gulimall.product.service.CategoryBrandRelationService;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.checkerframework.checker.units.qual.K;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fujin.common.utils.PageUtils;
import com.fujin.common.utils.Query;

import com.fujin.gulimall.product.dao.CategoryDao;
import com.fujin.gulimall.product.entity.CategoryEntity;
import com.fujin.gulimall.product.service.CategoryService;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 通过缓存redis中查询分类信息、没有查调用查询数据库方法
     * @return
     */
    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        ValueOperations<String ,String> ops = redisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)){
            //在数据库中查询分类信息
            Map<String, List<Catelog2Vo>> catelogJsonDb = getCatelogJsonDbWithRedisLock();
            //先将分类信息转为json格式、最后将分类信息存入redis
            String s = JSON.toJSONString(catelogJsonDb);
            //数据存入redis，并设置过期时间1天
            redisTemplate.opsForValue().set("catalogJson",s,1, TimeUnit.DAYS);
            return catelogJsonDb;
        }
        //当在redis查询到数据直接使用、需要先将json数据转为需要的Map
        Map<String, List<Catelog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
        return listMap;
    }

    /**
     * 使用redisson作为分布式锁
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonDbWithRedissonLock() {
        //获取锁
        RLock lock = redissonClient.getLock("CatelogJson-lock");
        lock.lock();
        Map<String, List<Catelog2Vo>> catelogJsonDb = null;
        try {
            //拿到锁，执行业务
            catelogJsonDb = getCatelogJsonDb();
        }finally {
            lock.unlock();
        }
        return catelogJsonDb;   //返回
    }

    /**
     * 使用redis作为分布式锁
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonDbWithRedisLock() {
        //获取uuid作为锁的值
        String uuid = UUID.randomUUID().toString();
        //1、一进来就占用分布式锁、去redis占用
        //使用setNX:只有键key不存在的时候才会设置key的值,设置超时时间300s
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if (lock){
            //拿到锁，执行业务
            Map<String, List<Catelog2Vo>> catelogJsonDb = getCatelogJsonDb();
            //获取锁的值
            String lockValue = (String) redisTemplate.opsForValue().get("lock");

            //获取lock值和删除必须在一个原子中操作,使用lua脚本
            String script  = "if redis.call(\"get\",KEYS[1]) == ARGV[1] \n" +
                    "then\n" +
                    "\treturn redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end;\n";
            redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);

            return catelogJsonDb;   //返回
        }else {
            //加锁失败。。。重试
            return getCatelogJsonDbWithRedisLock();    //自旋的方式
        }
    }

    /**
     * 数据库查询分类信息
     * @return
     */
    private Map<String, List<Catelog2Vo>> getCatelogJsonDb() {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");

        //当缓存不为空的时候直接返回
        if (!StringUtils.isEmpty(catalogJson)) {
            Map<String, List<Catelog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return listMap;
        }
        //查询所有分类信息
        List<CategoryEntity> entityList = baseMapper.selectList(null);

        //查询所有一级分类
        List<CategoryEntity> level1Catagories = getCategoryByParentCid(entityList, 0L);
        //根据一级分类查询二级分类
        Map<String, List<Catelog2Vo>> listMap = level1Catagories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //查询每个一级分类下的二级分类
            List<CategoryEntity> categoryEntityList = getCategoryByParentCid(entityList, v.getCatId());

            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntityList != null) {
                //解析封装二级分类vo
                catelog2Vos = categoryEntityList.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatLevel().toString(), null, l2.getCatId().toString(), v.getName());

                    //根据二级分类找到三级分类
                    List<CategoryEntity> categoryEntityList1 = getCategoryByParentCid(entityList, l2.getCatId());
                    if (categoryEntityList1 != null) {
                        //解析封装三级分类vo
                        List<Catelog2Vo.Catalog3> collect = categoryEntityList1.stream().map(l3 -> {
                            Catelog2Vo.Catalog3 catalog3 = new Catelog2Vo.Catalog3(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());

                            return catalog3;
                        }).collect(Collectors.toList());

                        catelog2Vo.setCatalog3List(collect);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));

        //将查到的数据存入缓存
        //先将分类信息转为json格式、最后将分类信息存入redis
        String s = JSON.toJSONString(listMap);
        //数据存入redis，并设置过期时间1天
        redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
        return listMap;
    }

    /**
     * 本地锁synchronized
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonDbWithLocalLock() {
        synchronized (this) {
            return getCatelogJsonDb();
        }
    }

    public List<CategoryEntity> getCategoryByParentCid(List<CategoryEntity> entityList,Long catId){
        List<CategoryEntity> collect = entityList.stream().filter(item -> {
            return item.getParentCid() == catId;
        }).collect(Collectors.toList());

        return collect;
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //查询所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);

        //取出所有一级分类
        List<CategoryEntity> collect = categoryEntities.stream().filter(item -> {
            return item.getParentCid() == 0;
        }).map(menu -> {
            //递归获取子分类
            menu.setChildren(getChildren(menu, categoryEntities));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

    /**
     * 递归获取所有子分类
     * @param categoryEntity：当前分类
     * @param all:所有分类
     * @return
     */
    public List<CategoryEntity> getChildren(CategoryEntity categoryEntity,List<CategoryEntity> all){
        //根据当前分类
        List<CategoryEntity> collect = all.stream().filter(item -> {
            return item.getParentCid() == categoryEntity.getCatId();
        }).map(menu -> {
            menu.setChildren(getChildren(menu, all));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return collect;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO 1 检查当前的菜单是否被别的地方所引用

        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findPathByattrGroupId(Long attrGroupId) {
        //使用递归查询其父id并将其返回
        List<Long> list = new ArrayList<>();

        list = getcategoryIds(attrGroupId, list);
        // 收集的时候是顺序 前端是逆序显示的 所以用集合工具类给它逆序一下
        Collections.reverse(list);

        return list.toArray(new Long[list.size()]);
    }

    @CacheEvict(value = {"Category"},allEntries = true)
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())){
            //修改分类名称时 并修改其他关联数据
            categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
        }
    }

    //每一个需要缓存的数据我们都需要指定放到哪个缓存分区(通常按照业务划分)
    @Cacheable(value = "Category",key = "'Level1Catagories'")  //代表当前方法的结果需要缓存，如果缓存有整个方法都不需要调用，直接拿缓存里面的
    @Override
    public List<CategoryEntity> getLevel1Catagories() {
        List<CategoryEntity> Level1Catagories = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));

        return Level1Catagories;
    }

    public List<Long> getcategoryIds(Long attrGroupId,List<Long> list){
        list.add(attrGroupId);
        CategoryEntity categoryEntity = this.getById(attrGroupId);
        //ParentCid不等于0的时候说明他不是最上级节点
        if (categoryEntity.getParentCid() != 0){
            getcategoryIds(categoryEntity.getParentCid(),list);
        }
        return list;
    }
}